﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows;
using System.Linq.Expressions;
using System.Windows.Input;
using System.Windows.Threading;
using System.ComponentModel;

namespace TUtils.Extensions
{
	/// <summary>
	/// Extensions to DependencyObject, CommandBindingCollection
	/// </summary>
	public static class TWPFHelper
	{
		public static bool IsInDesignMode
		{
			get
			{
				return (bool)(DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue);
			}
		}

		/// <summary>
		/// helper for setting typesafely a binding to a property of a source object.
		/// The Binding-Mode is OneWay.
		/// </summary>
		/// <typeparam name="T">
		/// type of the source object
		/// </typeparam>
		/// <typeparam name="TProp">type of the property, which is to be bounded.</typeparam>
		/// <param name="target">
		/// the target dependency object
		/// </param>
		/// <param name="dependencyPropertyOfTarget">
		/// the target dependency property
		/// </param>
		/// <param name="sourceObject">
		/// the source object, which typically is inherited from type DependencyObject
		/// or implements INotifyPropertyChanged.
		/// </param>
		/// <param name="propertySelector">
		/// a typesafe selector, which selects the source property, which is to
		/// be bound to the target property.
		/// </param>
		/// <returns></returns>
		public static BindingExpressionBase SetBinding<T, TProp>(
			this DependencyObject target,
			DependencyProperty dependencyPropertyOfTarget,
			T sourceObject,
			Expression<Func<T, TProp>> propertySelector
			)
		{
			return target.SetBinding(
				dependencyPropertyOfTarget,
				sourceObject,
				propertySelector,
				null);
		}

		/// <summary>
		/// helper for setting typesafely a binding to a property of a source object.
		/// </summary>
		/// <typeparam name="T">
		/// type of the source object
		/// </typeparam>
		/// <typeparam name="TProp">type of the property, which is to be bounded.</typeparam>
		/// <param name="target">
		/// the target dependency object
		/// </param>
		/// <param name="dependencyPropertyOfTarget">
		/// the target dependency property
		/// </param>
		/// <param name="sourceObject">
		/// the source object, which typically is inherited from type DependencyObject
		/// or implements INotifyPropertyChanged.
		/// </param>
		/// <param name="propertySelector">
		/// a typesafe selector, which selects the source property, which is to
		/// be bound to the target property.
		/// </param>
		/// <param name="bindingMode">
		/// mode of binding
		/// </param>
		/// <returns></returns>
		public static BindingExpressionBase SetBinding<T, TProp>(
			this DependencyObject target,
			DependencyProperty dependencyPropertyOfTarget,
			T sourceObject,
			Expression<Func<T, TProp>> propertySelector,
			BindingMode bindingMode
			)
		{
			return target.SetBinding(
				dependencyPropertyOfTarget,
				sourceObject,
				propertySelector,
				binding => { binding.Mode = bindingMode; }
				);
		}

		/// <summary>
		/// helper for setting a binding to a dependency property of a source object.
		/// The Binding-Mode is OneWay.
		/// </summary>
		/// <typeparam name="T">
		/// type of the source object
		/// </typeparam>
		/// <param name="target">
		/// the target dependency object
		/// </param>
		/// <param name="dependencyPropertyOfTarget">
		/// the target dependency property
		/// </param>
		/// <param name="sourceObject">
		/// the source object, which typically is inherited from type DependencyObject
		/// or implements INotifyPropertyChanged.
		/// </param>
		/// <param name="dependencyPropertyOfSource">
		/// dependency property of the source object
		/// </param>
		/// <returns></returns>
		public static BindingExpressionBase SetBinding<T>(
			this DependencyObject target,
			DependencyProperty dependencyPropertyOfTarget,
			T sourceObject,
			DependencyProperty dependencyPropertyOfSource
			)
			where T : DependencyObject
		{
			return target.SetBinding(
				dependencyPropertyOfTarget,
				sourceObject,
				dependencyPropertyOfSource,
				null);
		}

		/// <summary>
		/// helper for setting a binding to a dependency property of a source object.
		/// </summary>
		/// <typeparam name="T">
		/// type of the source object
		/// </typeparam>
		/// <param name="target">
		/// the target dependency object
		/// </param>
		/// <param name="dependencyPropertyOfTarget">
		/// the target dependency property
		/// </param>
		/// <param name="sourceObject">
		/// the source object, which typically is inherited from type DependencyObject
		/// or implements INotifyPropertyChanged.
		/// </param>
		/// <param name="dependencyPropertyOfSource">
		/// dependency property of the source object
		/// </param>
		/// <param name="bindingMode"></param>
		/// <returns></returns>
		public static BindingExpressionBase SetBinding<T>(
			this DependencyObject target,
			DependencyProperty dependencyPropertyOfTarget,
			T sourceObject,
			DependencyProperty dependencyPropertyOfSource,
			BindingMode bindingMode
			)
			where T : DependencyObject
		{
			return target.SetBinding(
				dependencyPropertyOfTarget,
				sourceObject,
				dependencyPropertyOfSource,
				binding => { binding.Mode = bindingMode; });
		}

		/// <summary>
		/// helper for setting a binding to a dependency property of a source object.
		/// Allows to configure the binding.
		/// </summary>
		/// <typeparam name="T">
		/// type of the source object
		/// </typeparam>
		/// <param name="target">
		/// the target dependency object
		/// </param>
		/// <param name="dependencyPropertyOfTarget">
		/// the target dependency property
		/// </param>
		/// <param name="sourceObject">
		/// the source object, which typically is inherited from type DependencyObject
		/// or implements INotifyPropertyChanged.
		/// </param>
		/// <param name="dependencyPropertyOfSource">
		/// dependency property of the source object
		/// </param>
		/// <param name="additionalBindingConfiguration">
		/// e.g.:
		/// binding => { binding.Mode = BindingMode.TwoWay; }
		/// </param>
		/// <returns></returns>
		public static BindingExpressionBase SetBinding<T>(
			this DependencyObject target,
			DependencyProperty dependencyPropertyOfTarget,
			T sourceObject,
			DependencyProperty dependencyPropertyOfSource,
			Action<Binding> additionalBindingConfiguration
			)
			where T: DependencyObject
		{
			var binding = new Binding()
			{
				Source = sourceObject,
				Path = new PropertyPath(dependencyPropertyOfSource.Name),
				Mode = BindingMode.OneWay
			};

			if (additionalBindingConfiguration != null)
				additionalBindingConfiguration(binding);

			return BindingOperations.SetBinding(
				target,
				dependencyPropertyOfTarget,
				binding);
		}

		/// <summary>
		/// helper for setting typesafely a binding to a property of a source object.
		/// Allows to configure the binding.
		/// </summary>
		/// <typeparam name="T">
		/// type of the source object
		/// </typeparam>
		/// <typeparam name="TProp">type of the property, which is to be bounded.</typeparam>
		/// <param name="target">
		/// the target dependency object
		/// </param>
		/// <param name="dependencyPropertyOfTarget">
		/// the target dependency property
		/// </param>
		/// <param name="sourceObject">
		/// the source object, which typically is inherited from type DependencyObject
		/// or implements INotifyPropertyChanged.
		/// </param>
		/// <param name="propertySelector">
		/// a typesafe selector, which selects the source property, which is to
		/// be bound to the target property.
		/// </param>
		/// <param name="additionalBindingConfiguration">
		/// e.g.:
		/// binding => { binding.Mode = BindingMode.TwoWay; }
		/// </param>
		/// <returns></returns>
		public static BindingExpressionBase SetBinding<T, TProp>(
			this DependencyObject target,
			DependencyProperty dependencyPropertyOfTarget,
			T sourceObject,
			Expression<Func<T, TProp>> propertySelector,
			Action<Binding> additionalBindingConfiguration
			) 
		{
			var binding = new Binding()
			{
				Source = sourceObject,
				Path = new PropertyPath(sourceObject.GetMemberName(propertySelector)),
				Mode = BindingMode.OneWay
			};

			if (additionalBindingConfiguration != null)
				additionalBindingConfiguration(binding);

			return BindingOperations.SetBinding(
				target,
				dependencyPropertyOfTarget,
				binding);
		}


		/// <summary>
		/// Adds a handler to the CommandBindings-property of an UIElement.
		/// </summary>
		/// <param name="this">CommandBindings-property </param>
		/// <param name="command">
		/// command
		/// </param>
		/// <param name="onCommandHandler">
		/// void ExecutedRoutedEventHandler(object sender, ExecutedRoutedEventArgs e)
		/// </param>
		/// <param name="mayExecuteHandler">
		/// void CanExecuteRoutedEventHandler(object sender, CanExecuteRoutedEventArgs e)
		/// </param>
		public static void AddHandler(
			this CommandBindingCollection @this, 
			RoutedCommand command,
			ExecutedRoutedEventHandler onCommandHandler,
			CanExecuteRoutedEventHandler mayExecuteHandler
			)
		{
			@this.Add(new CommandBinding(
				command,
				onCommandHandler,
				mayExecuteHandler));
		}

		/// <summary>
		/// Adds a handler to the CommandBindings-property of an UIElement.
		/// </summary>
		/// <param name="this">CommandBindings-property </param>
		/// <param name="command">
		/// command
		/// </param>
		/// <param name="onCommandHandler">
		/// void ExecutedRoutedEventHandler(object sender, ExecutedRoutedEventArgs e)
		/// </param>
		public static void AddHandler(
			this CommandBindingCollection @this,
			RoutedCommand command,
			ExecutedRoutedEventHandler onCommandHandler
			)
		{
			@this.Add(new CommandBinding(
				command,
				onCommandHandler));
		}

		/// <summary>
		/// Adds a handler to the CommandBindings-property of the UIElement.
		/// </summary>
		/// <param name="this">CommandBindings-property </param>
		/// <param name="command">
		/// command
		/// </param>
		/// <param name="onCommandHandler">
		/// void ExecutedRoutedEventHandler(object sender, ExecutedRoutedEventArgs e)
		/// </param>
		/// <param name="mayExecuteHandler">
		/// void CanExecuteRoutedEventHandler(object sender, CanExecuteRoutedEventArgs e)
		/// </param>
		public static void AddHandler(
			this UIElement @this,
			RoutedCommand command,
			ExecutedRoutedEventHandler onCommandHandler,
			CanExecuteRoutedEventHandler mayExecuteHandler
			)
		{
			@this.CommandBindings.AddHandler(
				command,
				onCommandHandler,
				mayExecuteHandler);
		}

		/// <summary>
		/// Adds a handler to the CommandBindings-property of the UIElement.
		/// </summary>
		/// <param name="this">CommandBindings-property </param>
		/// <param name="command">
		/// command
		/// </param>
		/// <param name="onCommandHandler">
		/// void ExecutedRoutedEventHandler(object sender, ExecutedRoutedEventArgs e)
		/// </param>
		public static void AddHandler(
			this UIElement @this,
			RoutedCommand command,
			ExecutedRoutedEventHandler onCommandHandler
			)
		{
			@this.CommandBindings.AddHandler(
				command,
				onCommandHandler);
		}

		public static T SafelyInvoke<T>(
			this DispatcherObject dispatcherObj,
			Func<T> action)
		{
			if (dispatcherObj.CheckAccess())
				return action();

			T result = default(T);

			dispatcherObj.Dispatcher.Invoke(
				new Action(() =>
				{
					result = action();
				}));
			return result;
		}

		public static void BeginSafelyInvoke(
			this DispatcherObject dispatcherObj,
			Action action)
		{
			if (dispatcherObj.CheckAccess())
			{
				action();
				return;
			}

			dispatcherObj.Dispatcher.BeginInvoke(action);
		}

	}


}
